﻿using System;
using System.Collections.Generic;
using System.Device.Location;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Devices;
using Microsoft.Devices.Sensors;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Matrix = Microsoft.Xna.Framework.Matrix;
using System.Globalization;
using System.Diagnostics;
using STS.Model;

namespace AugmentedReality
{
    public partial class MainPageAR : PhoneApplicationPage
    {/// <summary>
        /// Język aplikacji
        /// </summary>
        string _Lang;

        /// <summary>
        /// Urządzenie kamery
        /// </summary>
        internal PhotoCamera _Camera;

        /// <summary>
        /// Ruchu/orientacja
        /// </summary>
        internal Motion _Motion;

        /// <summary>
        /// Okno renderowania, widok
        /// </summary>
        internal Viewport _Viewport;

        /// <summary>
        /// Macierz rzutowania
        /// </summary>
        internal Matrix _Projection;

        /// <summary>
        /// Macierz widoku
        /// </summary>
        internal Matrix _View;

        /// <summary>
        /// Obserwator współrzędnych geogaficznych
        /// </summary>
        internal GeoCoordinateWatcher _GeoWatcher;

        /// <summary>
        /// Aktualne współrzędne geograficzne urządzenia
        /// </summary>
        internal GeoCoordinate _ActualGeoLocation;

        /// <summary>
        /// Indeks ostatniej etykiety znalezionej najbliżej centrum ekranu
        /// </summary>
        int _LastFoundIndex = -1;

        /// <summary>
        /// Flaga do blokowania stanu okienka z informacjami o POI. Jeśli okienko jest zablokowane, informacja o Poi jest zafiksowana 
        /// i pojawiają się przyciski od nawigacji
        /// </summary>
        bool _BlockInfoBox = true;

        /// <summary>
        /// Aktualna orientacja urządzenia
        /// </summary>
        PageOrientation _CurrentOrientation = PageOrientation.PortraitUp;

        /// <summary>
        /// Lista współrzędnych Poi
        /// </summary>
        List<Vector3> _Points;

        /// <summary>
        /// Lista etykiet z obrazkami reprezentującymi Poi
        /// </summary>
        List<UIElement> _Labels;

        /// <summary>
        /// Lista Poi
        /// </summary>
        List<Poi> _Pois;

        /// <summary>
        /// Odłegłość w kierunku Z, w jakiej znajdują się etykiety (w jednostkach)
        /// </summary>
        int _WCSRadius = 12;

        double _MaxDistance;
        private object value;
        private Regex htmlRegex;

        /// <summary>
        /// Konstruktor inicjalizujący GUI
        /// </summary>
        public MainPageAR()
        {
            InitializeComponent();

            // Inicjalizacja POI
            this._Points = new List<Vector3>();
            this._Labels = new List<UIElement>();
            this._Pois = new List<Poi>();

            this.InfoBox.Visibility = Visibility.Collapsed;
            this.InfoBoxActionButtons.Visibility = Visibility.Collapsed;
            this.InfoBox.SetValue(Canvas.ZIndexProperty, 3);

            this.ImageRadar.Visibility = Visibility.Collapsed;
            this.CanvasRadar.Visibility = Visibility.Collapsed;

            // podpięcie zdarzeń
            this.InfoBoxContent.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(InfoBoxContent_Tap);
            this.InfoBoxHideBtn.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(InfoBoxHideBtn_Tap);
            this.InfoBoxDetailsBtn.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(InfoBoxDetailsBtn_Tap);
            this.InfoBoxMapBtn.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(InfoBoxMapBtn_Tap);
            this.OrientationChanged += new EventHandler<OrientationChangedEventArgs>(MainPageAR_OrientationChanged);

            htmlRegex = new Regex("<.*?>", RegexOptions.Compiled);
        }

        /// <summary>
        /// Obsługa zmiany orientacji urządzenia. Zapamietuje aktualną orientację w polu _CurrentOrientation,
        /// odpowiednio zmienia orientację widoku z kamery i inicjalizuje okno renderowania.
        /// </summary>
        /// <param name="sender">Żródło zdarzenia</param>
        /// <param name="e">Argumenty zdarzenia</param>
        void MainPageAR_OrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            this._CurrentOrientation = e.Orientation;

            switch (e.Orientation)
            {
                case PageOrientation.PortraitUp: // obrót widoku z kamery o 90 stopni
                    this.ViewfinderBrush.RelativeTransform = new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = 90 };
                    this.ImageRadar.Visibility = Visibility.Visible;
                    this.CanvasRadar.Visibility = Visibility.Visible;
                    break;
                case PageOrientation.PortraitDown: // obrót widoku z kamery o 270 stopni
                    this.ViewfinderBrush.RelativeTransform = new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = 270 };
                    break;
                case PageOrientation.LandscapeLeft: // domyśla orientacja dla widoku z kamery - nie trzeba obracać
                    this.ViewfinderBrush.RelativeTransform = new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = 0 };
                    break;
                case PageOrientation.LandscapeRight: // obrót widoku z kamery o 180 stopni
                    this.ViewfinderBrush.RelativeTransform = new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = 180 };
                    break;
            }
            InitializeViewport(e.Orientation);
        }

        /// <summary>
        /// Obsługa kliknięcia przycisku, odsyłającego do pokazania trasy do Poi na mapie
        /// </summary>
        /// <param name="sender">Żródło zdarzenia</param>
        /// <param name="e">Argumenty zdarzenia</param>
        void InfoBoxMapBtn_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            //string url = "http://www.bing.com/maps/explore/#5003/0.5040=rtp:"
            //+ "pos." + this._ActualGeoLocation.Latitude.ToString().Replace(',', '.') + "_"
            //+ this._ActualGeoLocation.Longitude.ToString().Replace(',', '.') + "_" + "START"
            //+ "~pos." + this._Pois[this._LastFoundIndex].Lat.ToString().Replace(',', '.')
            //+ "_" + this._Pois[this._LastFoundIndex].Lng.ToString().Replace(',', '.')
            //+ "_"
            //    //+ Uri.EscapeUriString(this._Pois[this._LastFoundIndex].Title) // problem z wyswietlaniem etykiet
            //+ "KONIEC"
            //+ "/5872/style=auto&lat="
            //+ this._ActualGeoLocation.Latitude.ToString().Replace(',', '.')
            //+ "&lon=" + this._ActualGeoLocation.Longitude.ToString().Replace(',', '.')
            //+ "&z=16";
            string url = "http://bing.com/maps/default.aspx?v=2&rtp="
              + "pos." + this._ActualGeoLocation.Latitude.ToString().Replace(',', '.') + "_"
              + this._ActualGeoLocation.Longitude.ToString().Replace(',', '.') + "_" + "START"
            + "~pos." + this._Pois[this._LastFoundIndex].Lat.ToString().Replace(',', '.')
            + "_" + this._Pois[this._LastFoundIndex].Lng.ToString().Replace(',', '.')
            + "_"
            + "KONIEC";

            WebBrowserTask webbrowser = new WebBrowserTask();
            webbrowser.Uri = new Uri(url, UriKind.Absolute);
            webbrowser.Show(); // przejście do mapy w Bing z wyznaczoną trasą           
        }

        /// <summary>
        /// Obsługa przycisku odsyłającego do szczegółowych inforamcji o Poi
        /// </summary>
        /// <param name="sender">Źródło zdarzenia</param>
        /// <param name="e">Argumenty zdarzenia</param>
        void InfoBoxDetailsBtn_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            // Odsyła do szczegółów etykiety najbliższej centrum
            //NavigationService.Navigate(new Uri("/PoiDetails.xaml?id=" + this._Pois[this._LastFoundIndex].PoiId + "&from=" + ARConstants.FROM_AR, UriKind.RelativeOrAbsolute));\
            Debug.WriteLine("Przejdz do opisu POIA");
        }

        /// <summary>
        /// Obsługa ukrywania warstwy z dodatkowymi przyciskami z 'Nawigacją' i 'Szczegółami Poi'.
        /// Ukrywa dodatkowe przyciski  i odblokowuje okienko z informacjami.
        /// </summary>
        /// <param name="sender">Źródło zdarzenia</param>
        /// <param name="e">Argumenty zdarzenia</param>
        void InfoBoxHideBtn_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            this._BlockInfoBox = false;
            this.InfoBoxActionButtons.Visibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Obsługa kliknięcia w okienko z informacjami.
        /// Blokuje okienko i wyświetla dodatkowe przyciski.
        /// </summary>
        /// <param name="sender">Źródło zdarzenia</param>
        /// <param name="e">Argumenty zdarzenia</param>
        void InfoBoxContent_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            this._BlockInfoBox = true;
            this.InfoBoxActionButtons.Visibility = Visibility.Visible;
        }


        /// <summary>
        /// Inicjalizuje okno renderowania w zależnosci od orientacji urządzenia.
        /// </summary>
        /// <param name="orientation">Orientacja urządzenia</param>
        public void InitializeViewport(PageOrientation orientation)
        {
            this._Viewport = new Viewport(0, 0, (int)this.ActualWidth, (int)this.ActualHeight); // obszar widoku
            float aspect = this._Viewport.AspectRatio; // współczynnik proporcji ekranu x/y

            // macierz rzutowania, parametry: pole widzenia w kierunku y (radiany), proporcje widoku,
            // odległość do przeniej powierzchni bryły obcięcia, odległość do tylnej powierzchni bryły obcięcia
            this._Projection = Matrix.CreatePerspectiveFieldOfView(1f, aspect, 1f, 12f);

            // macierz widoku, parametry: połozenie kamery, kierunek obserwacji, kierunek "do góry"
            switch (orientation)
            {
                case PageOrientation.PortraitUp:
                    this._View = Matrix.CreateLookAt(Vector3.UnitZ, Vector3.Zero, Vector3.Up);
                    break;
                case PageOrientation.PortraitDown:
                    this._View = Matrix.CreateLookAt(Vector3.UnitZ, Vector3.Zero, Vector3.Down);
                    break;
                case PageOrientation.LandscapeLeft:
                    this._View = Matrix.CreateLookAt(Vector3.UnitZ, Vector3.Zero, Vector3.Right);
                    break;
                case PageOrientation.LandscapeRight:
                    this._View = Matrix.CreateLookAt(Vector3.UnitZ, Vector3.Zero, Vector3.Left);
                    break;
            }
        }

        /// <summary>
        /// Obsługa wyjścia z trybu AR: zatrzymanie obiektu Motion.
        /// </summary>
        /// <param name="e">Argumenty zdarzenia</param>
        protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
        {
            if (this._Motion != null)
            {
                this._Motion.Stop();
                this._Motion.CurrentValueChanged -= Motion_CurrentValueChanged;
            }
            if (this._GeoWatcher != null)
                this._GeoWatcher.PositionChanged -= this.GeoWatcher_PositionChanged;
            base.OnNavigatedFrom(e);
        }

        /// <summary>
        /// Obsługa wyjścia z trybu AR: zatrzymanie obiektu Motion.
        /// </summary>
        /// <param name="e">Argumenty zdarzenia</param>
        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            if (this._Motion != null)
            {
                this._Motion.Stop();
                this._Motion.CurrentValueChanged -= Motion_CurrentValueChanged;
                this._Camera.Dispose();
            }
            if (this._GeoWatcher != null)
                this._GeoWatcher.Stop();
            //this._GeoWatcher.PositionChanged -= this.GeoWatcher_PositionChanged;
            base.OnNavigatingFrom(e);
        }

        /// <summary>
        /// Inicjalizacja AR po wejściu na głowną stronę biblioteki
        /// </summary>
        /// <param name="e">Argumenty zdarzenia</param>
        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            this.LayoutRoot.IsHitTestVisible = false;
            this._Camera = new PhotoCamera(); // inicjalizacja kamery
            this.ViewfinderBrush.RelativeTransform = new CompositeTransform() { CenterX = 0.5, CenterY = 0.5, Rotation = 90 }; // obrót kamery zgodnie z orientacją "portrait"
            this.ViewfinderBrush.SetSource(this._Camera); // podpięcie kamery jako źródła wypełnienia Rectangle            

            if (Motion.IsSupported) // obsługa ruchu o ile jest wspierana na urządzeniu
            {
                this._Motion = new Motion();

                this._Motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(250); // odświeżanie co 250 ms (wartość nie może być zbyt mała, by obliczenia zdązyły się wykonać
                this._Motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(Motion_CurrentValueChanged); // obsługa zmiany połozenia urządzenia
                this._Motion.Start(); // uruchomienie sensora Motion

                string valueString = "";
                NavigationContext.QueryString.TryGetValue("lang", out _Lang);
                if (NavigationContext.QueryString.TryGetValue("value", out valueString))
                {
                    value = valueString;
                }


                this._GeoWatcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High); // GPS
                if (this._GeoWatcher.Permission == GeoPositionPermission.Granted)
                {
                    this._GeoWatcher.MovementThreshold = 20; // zmiana położenia wywołująca zdarzenie PositionChanged
                    this._GeoWatcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(this.GeoWatcher_PositionChanged);
                    this._GeoWatcher.Start();
                }

            }
            base.OnNavigatedTo(e);
        }

        /// <summary>
        /// Obsługa zmiany położenia (współrzędnych geograqficznych) urządzenia
        /// </summary>
        /// <param name="sender">Źródło zdarzenia</param>
        /// <param name="e">Argumenty zdarzenia</param>
        protected void GeoWatcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)//obsłuzyć suwak - pobrać listę poi dla zadanej odległosci
        {
            if (this._GeoWatcher.Status == GeoPositionStatus.Ready) // GPS gootowy
            {
                this._GeoWatcher.Stop(); // zatrzymanie GPS
                this._ActualGeoLocation = e.Position.Location; // ustawienie aktualnej pozycji geograficznej urządzenia
                this.LayoutRoot.IsHitTestVisible = true;
                BtnOK_Click(null, new RoutedEventArgs());
            }
        }

        /// <summary>
        /// Obsługa zmiany położenia urządzenia. Ponieważ zmiany mają wpływ na GUI, wywołanie obsługi w BeginInvoke.
        /// </summary>
        /// <param name="sender">Źródło zdarzenia</param>
        /// <param name="args">Argumenty zdarzenia</param>
        protected void Motion_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> args)
        {
            Dispatcher.BeginInvoke(() => CurrentValueChanged(args.SensorReading, this._CurrentOrientation));
        }

        /// <summary>
        /// Obsługa zmiany położenia urządzenia
        /// </summary>
        /// <param name="reading">Zmiana położenia urządzenia</param>
        /// <param name="orientation">Orientacja urządzenia</param>
        protected void CurrentValueChanged(MotionReading reading, PageOrientation orientation)
        {
            if (this._Pois.Count == 0) // jesli nie ma Poi, nie ma nic do roboty
                return;



            if (this._Viewport.Width == 0)
                this.InitializeViewport(this._CurrentOrientation);

            this.RotateCanvasRadar(reading);
            // Pobranie RotationMatrix z MotionReading.
            // Obrót o 90 st. wokół osi X i przypisanie do układu współrzędnych XNA Framework
            Matrix attitude = Matrix.CreateRotationX(MathHelper.PiOver2) * reading.Attitude.RotationMatrix;

            for (int i = 0; i < this._Points.Count; i++) // zmiana położenia na ekranie wszystkich etykiet
            {
                Matrix world = new Matrix(); // macierz świata utworzona w zależności od orientacji urządzenia
                switch (orientation)
                {
                    case PageOrientation.PortraitUp:
                        world = Matrix.CreateWorld(this._Points[i], Vector3.UnitZ, Vector3.Up);
                        break;
                    case PageOrientation.PortraitDown:
                        world = Matrix.CreateWorld(this._Points[i], Vector3.UnitZ, Vector3.Down);
                        break;
                    case PageOrientation.LandscapeLeft:
                        world = Matrix.CreateWorld(this._Points[i], Vector3.UnitZ, Vector3.Right);
                        break;
                    case PageOrientation.LandscapeRight:
                        world = Matrix.CreateWorld(this._Points[i], Vector3.UnitZ, Vector3.Left);
                        break;
                }
                Vector3 projected = this._Viewport.Project(Vector3.Zero, this._Projection, this._View, world * attitude); // rzutowanie punktów z przestrzeni 3D do współrzędnych ekranu
                if (projected.Z > 1.01 || projected.Z < 0) // punkt jest poza zasięgiem kamery (jest z tyłu) - ukrycie elementów interfejsu (1.01 - bład zaokrągleń...)
                {
                    this._Labels[i].Visibility = Visibility.Collapsed;
                }
                else
                {
                    this._Labels[i].Visibility = Visibility.Visible;
                    TranslateTransform translateTransform = new TranslateTransform();
                    translateTransform.X = projected.X - (this._Labels[i].RenderSize.Width / 2);
                    translateTransform.Y = projected.Y - (this._Labels[i].RenderSize.Height / 2);
                    this._Labels[i].RenderTransform = translateTransform; // przesunięcie etykiet do nowej pozycji
                }
            }

            if (!this.IsAnyLabelDisplayed() && !this._BlockInfoBox)
            {
                this.InfoBox.Visibility = Visibility.Collapsed; // jeśli nie ma wyświetlanej żadnej etykiety i okno z informacjami nie jest zablokowane, to jest uktywane 
                return;
            }
            else if (this.IsAnyLabelDisplayed() && !this._BlockInfoBox)
            {
                this.InfoBox.Visibility = Visibility.Visible;// jeśli są wyświetlane etykiety i okno z informacjami nie jest zablokowane, to jest wyświetlane 
                int index = this.GetCenteredPoi(orientation); // pobranie indeksu Poi leżącego najbliżej środka ekranu
                this.DisplayInfoBox(index);
            }
        }

        /// <summary>
        /// Dodaje do UI etykietę związaną z POI
        /// </summary>
        /// <param name="position">Pozycja POI</param>
        /// <param name="imageUri">Adres obrazka dla etykiety</param>
        protected void AddLabel(string imageUri)
        {
            Border border = new Border();

            border.Name = "label" + this._Labels.Count;
            border.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
            border.VerticalAlignment = System.Windows.VerticalAlignment.Top;
            ImageBrush imageBrush = new ImageBrush();
            imageBrush.Stretch = Stretch.UniformToFill;

            BitmapImage bmp = new BitmapImage();
            bmp.CreateOptions = BitmapCreateOptions.BackgroundCreation;
            bmp.UriSource = new Uri(imageUri, UriKind.Relative);
            imageBrush.ImageSource = bmp;
            border.Background = imageBrush;

            border.Height = 60;
            border.Width = 60;

            border.BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 255, 255, 255));
            border.BorderThickness = new Thickness(5);
            border.CornerRadius = new CornerRadius(7);
            border.BorderBrush.Opacity = 0;
            border.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(Label_Tap);

            LayoutRoot.Children.Add(border);
            this._Labels.Add(border);
            this._Labels[this._Labels.Count - 1].Visibility = Visibility.Collapsed;
        }

        protected void Label_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            this.RemoveBorderFromLabel(this._LastFoundIndex);
            string name = ((Border)sender).Name;
            this.DisplayInfoBox(int.Parse(name.Substring(5)));
            this.InfoBoxContent_Tap(sender, e);
        }

        private void RotateCanvasRadar(MotionReading reading)
        {
            int angle = GetCameraViewDirection(reading);
            RotateTransform rt = new RotateTransform();
            rt.CenterX = this.CanvasRadar.Width / 2;
            rt.CenterY = this.CanvasRadar.Height / 2;
            rt.Angle = -angle;
            this.CanvasRadar.RenderTransform = rt;
        }

        /// <summary>
        /// Pobiera indeks etykiety (i POI) wyświetlanej najbliżej centrum ekranu
        /// </summary>
        /// <returns>Indeks etykiety (i POI) wyświetlanej najbliżej centrum ekranu</returns>
        private int GetCenteredPoi(PageOrientation orientation)
        {
            var screenSize = Application.Current.RootVisual.RenderSize; // rozmiar ekranu

            // wyszukanie etykiety wyswietlanej najbliżej centrum ekranu
            IEnumerable<UIElement> visibleLabels = null;
            switch (orientation)
            {
                case PageOrientation.Portrait:
                case PageOrientation.PortraitDown:
                case PageOrientation.PortraitUp:
                    visibleLabels = (from l in this._Labels
                                     let x = l.TransformToVisual(Application.Current.RootVisual).Transform(new System.Windows.Point(0, 0)).X - screenSize.Width / 2
                                     let y = l.TransformToVisual(Application.Current.RootVisual).Transform(new System.Windows.Point(0, 0)).Y - screenSize.Height / 2
                                     let distance = Math.Pow(x, 2) + Math.Pow(y, 2)
                                     where l.Visibility == Visibility.Visible
                                     orderby distance ascending
                                     select l).Take(1);
                    break;
                case PageOrientation.Landscape:
                case PageOrientation.LandscapeLeft:
                case PageOrientation.LandscapeRight:
                    visibleLabels = (from l in this._Labels
                                     let x = l.TransformToVisual(Application.Current.RootVisual).Transform(new System.Windows.Point(0, 0)).X - screenSize.Height / 2
                                     let y = l.TransformToVisual(Application.Current.RootVisual).Transform(new System.Windows.Point(0, 0)).Y - screenSize.Width / 2
                                     let distance = Math.Pow(x, 2) + Math.Pow(y, 2)
                                     where l.Visibility == Visibility.Visible
                                     orderby distance ascending
                                     select l).Take(1);
                    break;
            }
            UIElement focused = null; // pobranie etykiety najbliższej centrum ekranu
            if (visibleLabels != null && visibleLabels.Count<UIElement>() > 0)
                focused = visibleLabels.First<UIElement>();

            // indeks znalezionej etykiety i odpowiadającemu jej POI
            int index = -1;
            if (focused != null)
                index = this._Labels.IndexOf(focused);

            return index;
        }

        /// <summary>
        /// Sprawdza, czy jakakolwiek etykieta jest widoczna na ekranie
        /// </summary>
        /// <returns>True jeśli choć jedna etykieta jest wyświetlana, false w przeciwnym razie</returns>
        private bool IsAnyLabelDisplayed()
        {
            var screenSize = Application.Current.RootVisual.RenderSize;

            var visibleLabels = (from l in this._Labels
                                 let x = l.TransformToVisual(Application.Current.RootVisual).Transform(new System.Windows.Point(0, 0)).X
                                 let y = l.TransformToVisual(Application.Current.RootVisual).Transform(new System.Windows.Point(0, 0)).Y
                                 where l.Visibility == Visibility.Visible
                                 && x + l.RenderSize.Width > 0 && x < screenSize.Width && y + l.RenderSize.Height > 0 && y < screenSize.Height
                                 select l).Take(1);
            if (visibleLabels != null && visibleLabels.Count<UIElement>() > 0)
                return true;

            return false;
        }

        /// <summary>
        /// Obsługa kliknięcia przycisku ustawień AR. Ukrywa okienko z informacjami o POI, blokuje je i wyświtla okienko z ustawieniami AR.
        /// </summary>
        /// <param name="sender">Źródło zdarzenia</param>
        /// <param name="e">Argumenty</param>
        private void ARSettingsBtn_Click(object sender, RoutedEventArgs e)
        {
            this.InfoBox.Visibility = Visibility.Collapsed;
            this._BlockInfoBox = true;
        }

        /// <summary>
        /// Obsługa przycisku "OK" z okienka z ustawieniami AR. Zamyka okienko ustawień, pobiera listę POI dla aktualnych ustawień
        /// i odblokowuje okienko z informacjami o POI.
        /// </summary>
        /// <param name="sender">Źródło zdarzenia</param>
        /// <param name="e">Argumenty zdarzenia</param>
        private void BtnOK_Click(object sender, RoutedEventArgs e)
        {
            this.ImageRadar.Visibility = Visibility.Visible;
            this.CanvasRadar.Visibility = Visibility.Visible;

            for (int i = 0; i < this._Labels.Count; i++) // usunięcie etykiet z GUI
            {
                LayoutRoot.Children.Remove(this._Labels[i]);
            }
            // wyczyszczenie list z POI, etykietami i współrzędnymi etykiet
            this._Labels.Clear();
            this._Points.Clear();
            this._Pois.Clear();
            this._LastFoundIndex = -1;

            // pobranie listy POI dla zadanej odległości, usunięcie zasłonietych, ustawienie punktów i etykiet
            List<Poi> poiList = new List<Poi>();
            //foreach (var item in ModelInstance.PoiViewModel.PoiItems)
            //{
            //    if (item.Lat.HasValue || item.Lng.HasValue)
            //    {
            //        poiList.Add(item);
            //    }
            //}
            foreach (var item in ModelOfPoi.Pois)
            {
                if (item.Lat.HasValue || item.Lng.HasValue)
                {
                    poiList.Add(item);
                }
            }

            this.GetNonOverlappingPois(poiList);

            this._BlockInfoBox = false; // odblokowanie okienka z informacjami o POI
        }

        /// <summary>
        /// Usuwa z listy Poi całkowicie zasłonięte, znajdujące się w dalszym planie obiekty. Ustawia etykiety i współrzędne dla nowej listy Poi.
        /// </summary>
        /// <param name="all">Lista wszystkich Poi</param>
        private void GetNonOverlappingPois(List<Poi> all)
        {
            if (all != null && all.Count > 0)
            {
                this.DrawPoisOnCanvas(all, 60);
                this._Pois = new List<Poi>();
                this._Points = new List<Vector3>();
                this._Pois.Add(all[0]);
                var geoLocation = new GeoCoordinate((double)all[0].Lat, (double)all[0].Lng);
                this._Points.Add(Conversion.PolarXZToVector(Math.Round(Conversion.CalculateBearing(geoLocation, _ActualGeoLocation, AngleUnits.Degrees)), _WCSRadius * geoLocation.GetDistanceTo(_ActualGeoLocation) / _MaxDistance));//_WCSRadius));
                AddLabel(ModelInstance.PoiViewModel.GetPoiImage(all[0].PoiId));
                for (int i = 1; i < all.Count; i++)
                {
                    geoLocation = new GeoCoordinate((double)all[i].Lat, (double)all[i].Lng);
                    var point = Conversion.PolarXZToVector(Math.Round(Conversion.CalculateBearing(geoLocation, _ActualGeoLocation, AngleUnits.Degrees)), _WCSRadius * geoLocation.GetDistanceTo(_ActualGeoLocation) / _MaxDistance);

                    if (!this._Points.Contains(point))
                    {
                        this._Pois.Add(all[i]);
                        this._Points.Add(point);
                        AddLabel(ModelInstance.PoiViewModel.GetPoiImage(all[i].PoiId));
                    }
                }
            }
        }

        private void AddBorderToLabel(int index)
        {
            ((Border)this._Labels[index]).BorderBrush.Opacity = 0.7;
        }

        private void RemoveBorderFromLabel(int index)
        {
            ((Border)this._Labels[index]).BorderBrush.Opacity = 0;
        }

        private void DisplayInfoBox(int index)
        {
            if (index == -1)
                return;
            if (this._LastFoundIndex != index)
            {
                // wyświetlenie informacji o Poi w okienku
                if (this._LastFoundIndex != -1)
                    this.RemoveBorderFromLabel(this._LastFoundIndex);
                this._LastFoundIndex = index;
                string poiContentWithoutHTML = HttpUtility.HtmlDecode(this._Pois[index].Content);
                poiContentWithoutHTML = htmlRegex.Replace(poiContentWithoutHTML, string.Empty);
                poiContentWithoutHTML = poiContentWithoutHTML.Trim();
                this.InfoBoxContent.Text = poiContentWithoutHTML.Length > 200 ? poiContentWithoutHTML.Substring(0, 200) + "..." : poiContentWithoutHTML;
                this.InfoBoxTitle.Text = this._Pois[index].Name.Length > 50 ? this._Pois[index].Name.Substring(0, 50) + "..." : this._Pois[index].Name;
                this.InfoBoxDistance.Text = ((new GeoCoordinate((double)this._Pois[index].Lat, (double)this._Pois[index].Lng)).GetDistanceTo(this._ActualGeoLocation) / 1000.0).ToString("0.0") + " km";
                BitmapImage bmp = new BitmapImage();
                bmp.CreateOptions = BitmapCreateOptions.BackgroundCreation;
                bmp.UriSource = new Uri(ModelInstance.PoiViewModel.GetPoiImage(this._Pois[index].PoiId), UriKind.Relative);
                this.InfoBoxImage.Source = bmp;
            }
            AddBorderToLabel(index);
        }

        private void ARSettingsBtn_ManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e)
        {
            Image image = (Image)sender;
            switch (image.Name)
            {
                case "InfoBoxHideBtn":
                    image.Source = new BitmapImage(new Uri("fileClose2.png", UriKind.Relative));
                    break;
                case "ARSettingsBtn":
                    image.Source = new BitmapImage(new Uri("settings2.png", UriKind.Relative));
                    break;
            }
        }

        private void ARSettingsBtn_ManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e)
        {
            Image image = (Image)sender;
            switch (image.Name)
            {
                case "InfoBoxHideBtn":
                    image.Source = new BitmapImage(new Uri("fileClose.png", UriKind.Relative));
                    break;
                case "ARSettingsBtn":
                    image.Source = new BitmapImage(new Uri("settings.png", UriKind.Relative));
                    break;
            }
        }

        private void DrawPoisOnCanvas(List<Poi> pois, double distance)
        {
            this.CanvasRadar.Children.Clear();
            _MaxDistance = (from poi in pois
                            where poi.Lat.HasValue && poi.Lng.HasValue
                            let dst = (new GeoCoordinate((double)poi.Lat, (double)poi.Lng)).GetDistanceTo(_ActualGeoLocation)
                            orderby dst descending
                            select dst).First<double>();

            foreach (var poi in pois)
            {
                if (poi.Lat.HasValue && poi.Lng.HasValue)
                {
                    Ellipse point = new Ellipse();
                    var coordinate = new GeoCoordinate((double)poi.Lat, (double)poi.Lng);
                    var r = coordinate.GetDistanceTo(_ActualGeoLocation);
                    var position = Conversion.PolarXZToVector(Math.Round(Conversion.CalculateBearing(coordinate, _ActualGeoLocation, AngleUnits.Degrees)), r);
                    point.Fill = new SolidColorBrush(Colors.White);
                    point.Height = 8;
                    point.Width = 8;
                    point.Stroke = new SolidColorBrush(Colors.LightGray);
                    point.StrokeThickness = 1;
                    point.Margin = new Thickness(distance + position.X / _MaxDistance * (distance - 8), distance + position.Z / _MaxDistance * (distance - 8), 0, 0);
                    this.CanvasRadar.Children.Add(point);
                }
            }
        }

        private int GetCameraViewDirection(MotionReading reading)
        {
            var yaw = (int)MathHelper.ToDegrees(reading.Attitude.Yaw);
            var roll = (int)MathHelper.ToDegrees(reading.Attitude.Roll);
            var pitch = (int)MathHelper.ToDegrees(reading.Attitude.Pitch);

            if (roll < -20 && roll > -160)
            {
                return Normalize(360 - yaw + 90);
            }
            else if (roll > 20 && roll < 160)
            {
                return Normalize(360 - yaw - 90);
            }
            else if (pitch > 20 && pitch < 160)
            {
                return Normalize(-yaw);
            }
            else if (pitch < -20 && pitch > -160)
            {
                return Normalize(360 - yaw + 180);
            }

            return -1;
        }

        private int Normalize(int compassDirection)
        {
            if (compassDirection > 360)
                compassDirection -= 360;

            if (compassDirection < 0)
                compassDirection += 360;

            return compassDirection;
        }
    }
}